#####################################################################
# Product:      KnowledgeEngine (Plone Edition)
#
# File:         CreditManagementService.py
#
# Copyright:    Copyright (c) 2004, Faulkner Technologies
#
# Author:       Brian Oliver
#
# Description:  Defines an abstract service that is used to manage
#               permission/actor credits (like a bank account).
#
#               Primarily used in conjunction with a SecurityService,
#               this service enables tracking, management and auditing
#               of Permission 'credits' for actors (Groups or Users)
#               within the Knowledge Engine.
#
#               Credits for each 'Account' (where an account is 
#               a tuple of permission, securable and an actor) are 
#               managed like a bank account in that is there is no 
#               single attribute that represents the current
#               balance.  Rather the current balance of an 'account' is 
#               the sum of the deposits and withdrawals.
#
# See Also:     Service, SecurityService, Permission
#####################################################################

#####################################################################
## KnowledgeEngine Library Imports
from Products.KnowledgeEngine.Core import *
from Products.KnowledgeEngine.Common import *
from Products.KnowledgeEngine.Service import ServiceInfo, Service
from Products.KnowledgeEngine.Model.Model import Model
from Products.KnowledgeEngine.Permission import *

#####################################################################
## Zope Library Imports
from Products.CMFCore.utils import getToolByName
 
#####################################################################
## Python Library Imports
from time import time

#####################################################################
## CreditManagementServiceInfo Class Definition
class CreditManagementServiceInfo(ServiceInfo):
    """
        Defines the security service meta-information
    """
    
    #---------------------------------------------------------------#
    
    def getClassName(self):
        """
            Returns the name of the class for which 
            we are providing information.
        """
        return 'CreditManagementService'


    #---------------------------------------------------------------#

    def getDisplayName(self):
        """
            Return a friendly name for the class instances
            (this is used for displaying in lists etc)
        """
    
        return "KnowledgeEngine: Credit Management Service"
        
    #---------------------------------------------------------------#

    def getServiceName(self):
        """
            ABSTRACT: Return the lookup-identity.
            
            This method must be overriden to provide a unique service name
            (this name is used to set the zope id of the service
            when created)
        """
        
        return "knowledgeengine_creditmanagementservice"

#####################################################################
## CreditManagementService Class Definition
class CreditManagementService(Service):
    """
        Defines a tool that may be used to manage Knowledge Engine
        security
    """

    #---------------------------------------------------------------#

    #ABSTRACT TRANSIENT: a ServiceInfo implementation instance to provide info about this service
    _SERVICEINFO = CreditManagementServiceInfo()

    #---------------------------------------------------------------#

    def __init__(self):
        """
            Constructor for the object
        """
        
        #this is a stateless mix-in
        pass
        
    #---------------------------------------------------------------#
        
    def getEffectiveCredits(self, permission, securable, user):
        """
            FINAL: Returns the total credits currently available
            for the user and securable assuming the specified permission is 
            granted for that user.
            
            NOTE 1: This is the main entry point for determining
            the number of credits a USER has for something.  Use this
            method over other methods as it takes into account group
            and universal/generally defined (shared) credits.
            
            NOTE 2: This does MORE than just return the credits
            for the permissionIdentity, securable and
            actorIdentity.  Actually sums all of the possible
            credits (*, and groups) that may be consumed for 
            the permission
            
            Returns -1 if the credits aren't managed OR 
                          credits are not applicable for the permission
                          
                    0 if the permission is None OR there are no credits left
        """
        
        #do we have a permission, user and securable?
        if permission is None or user is None or securable is None:
            return 0
            
        #are we dealing with 'unmanaged credits'?
        elif CS_UNMANAGED in ([permission.getCreditStrategy()] + [aPermission.getCreditStrategy() for aPermission in permission.getLessEffectivePermissions()]):
            return -1
        
        #are we dealing with 'no credit management'?
        elif CS_NOT_APPLICABLE in ([permission.getCreditStrategy()] + [aPermission.getCreditStrategy() for aPermission in permission.getLessEffectivePermissions()]):
            return -1

        else:
            #credits are being managed... 

            #we need the groups to which the user belongs
            groups = getGroupProviderService(self).getGroupsForActor(user.getIdentity())

            #build a set of permission, securable and actor identities to sum which determine the effective credits
            effectiveSet = []
            
            #we add the most specific user tuple first
            effectiveSet = effectiveSet + [(permission.getIdentity(), securable.getIdentity(), user.getIdentity())]
            
            #we add the most specific groups tuple next
            effectiveSet = effectiveSet + [(permission.getIdentity(), securable.getIdentity(), group.getIdentity()) for group in groups]
            
            #we add the most general tuple for the user next
            effectiveSet = effectiveSet + [(permission.getIdentity(), '*', user.getIdentity())]

            #we add the most general tuple for the group next
            effectiveSet = effectiveSet + [(permission.getIdentity(), '*', group.getIdentity()) for group in groups]
            
            #we add the most general tuple for the securable next
            effectiveSet = effectiveSet + [(permission.getIdentity(), securable.getIdentity(), '*')]
            
            #we add the most general tuple last
            effectiveSet = effectiveSet + [(permission.getIdentity(), '*', '*')]

            #determine the effective credits by summing the credits in the effective set
            effectiveCredits = 0
            for (permissionIdentity, securableIdentity, actorIdentity) in effectiveSet:
                effectiveCredits = effectiveCredits + self.getAvailableCredits(permissionIdentity, securableIdentity, actorIdentity)

            return int(effectiveCredits)
        
    #---------------------------------------------------------------#

    def reduceEffectiveCredits(self, permission, securable, user, requestedByIdentity, narration = None):
        """
            FINAL: Reduces the number of credits for the specified
            user by 1 with the optionally provided narration
            
            NOTE 1: This is the main entry point for consuming
            credits for a USER.  Use this method over other methods 
            as it takes into account group and universal/generally 
            defined (shared) credits.
            
            NOTE 2: This method attempts to remove credits from
            the most specific credit 'supply' to the most general.
            eg: specific user, then group, then all.
            
            PARAMETERS: 
                requestedByIdentity - the identity of the actor requesting the credit reduction 
                narration - (optional) description/reason for the credit reduction
            
            RETURNS: String (the creditTransactionIdentity or None (if not successful))
            
            NOTE 3: Should the permission not require credit management, returns "" (empty string)
        """
        
        #we assume the result is failure
        result = None
        
        #do we have a permission, user and securable?
        if permission is None or user is None or securable is None:
            result = ""
        
        #are we dealing with 'unmanaged credits'?
        elif CS_UNMANAGED in ([permission.getCreditStrategy()] + [aPermission.getCreditStrategy() for aPermission in permission.getLessEffectivePermissions()]):
            result = ""
        
        #are we dealing with 'no credit management'?
        elif CS_NOT_APPLICABLE in ([permission.getCreditStrategy()] + [aPermission.getCreditStrategy() for aPermission in permission.getLessEffectivePermissions()]):
            result = ""

        else:
            #credits are being managed... 

            #we need the groups to which the user belongs
            groups = getGroupProviderService(self).getGroupsForActor(user.getIdentity())

            #build a set of permission, securable and actor identities from which we may reduce credits
            effectiveSet = []
            
            #we add the most specific user tuple first
            effectiveSet = effectiveSet + [(permission.getIdentity(), securable.getIdentity(), user.getIdentity())]
            
            #we add the most specific groups tuple next
            effectiveSet = effectiveSet + [(permission.getIdentity(), securable.getIdentity(), group.getIdentity()) for group in groups]
            
            #we add the most general tuple for the user next
            effectiveSet = effectiveSet + [(permission.getIdentity(), '*', user.getIdentity())]

            #we add the most general tuple for the group next
            effectiveSet = effectiveSet + [(permission.getIdentity(), '*', group.getIdentity()) for group in groups]
            
            #we add the most general tuple for the securable next
            effectiveSet = effectiveSet + [(permission.getIdentity(), securable.getIdentity(), '*')]
            
            #we add the most general tuple last
            effectiveSet = effectiveSet + [(permission.getIdentity(), '*', '*')]

            #attempt to withdraw the credits from the effective set
            for (permissionIdentity, securableIdentity, actorIdentity) in effectiveSet:

                #have we got a receipt transaction?
                if result is None:
                    #get the number of available credits for the current permission
                    availableCredits = self.getAvailableCredits(permissionIdentity, securableIdentity, actorIdentity)                

                    #are there available credits to remove?
                    if availableCredits > 0:
                        result = self.withdrawCredits(permissionIdentity, securableIdentity, actorIdentity, requestedByIdentity, 1, narration)
            
        return result
            
    #---------------------------------------------------------------#
        
    def getAvailableCredits(self, permissionIdentity, securableIdentity, actorIdentity):
        """
            ABSTRACT: Returns the number of currently available credits for the specified
            permission, securable, actor tuple.

            RETURN TYPE: Integer
        """            
    
        return 0
        
    #---------------------------------------------------------------#
        
    def depositCredits(self, permissionIdentity, securableIdentity, actorIdentity, createdBy, amount, narration = None):
        """
            ABSTRACT: Deposits the specified 'amount' of credits in the 'account' for the
            specified permission, securable and actor tuple.
            
            PARAMETERS:
                createdBy - used to track who is making the transaction (often the same as the actorIdentity, but not necessarily so)
                narration - free text comments about the transaction (typically not used)
            
            PRE-CONDITION:
                amount > 0
            
            RETURN TYPE: String (containing the creditTransactionIdentity that was generated for the transaction)            
        """            

        return generateIdentity("credittxn")
        
    #---------------------------------------------------------------#
        
    def withdrawCredits(self, permissionIdentity, securableIdentity, actorIdentity, createdBy, amount, narration = None):
        """
            ABSTRACT: Withdraws the specified 'amount' of credits in the 'account' for the
            specified permission, securable and actor tuple.
            
            PARAMETERS:
                createdBy - used to track who is making the transaction (often the same as the actorIdentity, but not necessarily so)
                narration - free text comments about the transaction (typically not used)
            
            PRE-CONDITION:
                amount > 0
            
            NOTE: Allows 'accounts' to be OVERDRAWN (ie: getAvailableCredits may return < 0)
            
            RETURN TYPE: String (containing the creditTransactionIdentity that was generated for the transaction)            
        """            

        return generateIdentity("credittxn")
        
    #---------------------------------------------------------------#
        
    def reverseCredits(self, creditTransactionIdentity, createdBy, narration = None):
        """
            ABSTRACT: Reverses a previously executed credit transaction (by posting an
            appropriate transaction for the -amount of the specified transaction)
            
            PARAMETERS:
                createdBy - used to track who is making the transaction
                narration - free text comments about the transaction (typically not used)
            
            RETURN TYPE: String (containing the creditTransactionIdentity that was generated for the transaction or None if not successful)            
        """            

        return generateIdentity("credittxn")
        
    #---------------------------------------------------------------#

#####################################################################
## Class Initialisation
# (none)
